﻿using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace TiaoTiaoCode.Common.ClassEntities
{
    /// <summary>
    /// 缓存
    /// </summary>
    public class TiaoTiaoMemoryCache<TItem> where TItem : class
    {
        #region 字段

        /// <summary>
        /// 缓存
        /// </summary>
        readonly MemoryCache _cache;

        /// <summary>
        /// 异步锁
        /// </summary>
        readonly ConcurrentDictionary<object, SemaphoreSlim> _locks;

        #endregion

        #region 属性

        /// <summary>
        /// 缓存项的统一缓存设置
        /// </summary>
        public MemoryCacheEntryOptions? EntryOptions { get; set; }

        #endregion

        public TiaoTiaoMemoryCache()
        {
            _cache = new MemoryCache(new MemoryCacheOptions());
            _locks = new ConcurrentDictionary<object, SemaphoreSlim>();
        }

        #region 创建缓存

        /// <summary>
        /// 创建缓存项
        /// </summary>
        /// <param name="key"></param>
        /// <param name="createItem"></param>
        /// <returns></returns>
        public async Task<TItem> Set(object key, Func<Task<TItem>> createItem)
        {
            TItem cacheEntry;

            if (!_cache.TryGetValue(key, out cacheEntry))// Look for cache key.
            {
                SemaphoreSlim mylock = _locks.GetOrAdd(key, k => new SemaphoreSlim(1, 1));

                await mylock.WaitAsync();
                try
                {
                    if (!_cache.TryGetValue(key, out cacheEntry))
                    {
                        // Key not in cache, so get data.
                        cacheEntry = await createItem();
                        _cache.Set(key, cacheEntry);
                    }
                }
                finally
                {
                    mylock.Release();
                }
            }
            return cacheEntry;
        }

        /// <summary>
        /// 创建缓存项
        /// </summary>
        /// <param name="key"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public async Task<TItem> Set(object key, TItem item)
        {
            TItem cacheEntry;

            if (!_cache.TryGetValue(key, out cacheEntry))// Look for cache key.
            {
                SemaphoreSlim mylock = _locks.GetOrAdd(key, k => new SemaphoreSlim(1, 1));

                await mylock.WaitAsync();
                try
                {
                    if (!_cache.TryGetValue(key, out cacheEntry))
                    {
                        if (EntryOptions == null)
                            _cache.Set(key, item, new MemoryCacheEntryOptions());
                        else
                            _cache.Set(key, item, EntryOptions);
                    }
                }
                finally
                {
                    mylock.Release();
                }
            }
            return cacheEntry;
        }

        /// <summary>
        /// 创建缓存项
        /// </summary>
        /// <param name="key"></param>
        /// <param name="item"></param>
        /// <param name="opt"></param>
        /// <returns></returns>
        public async Task<TItem> Set(object key, TItem item, MemoryCacheEntryOptions opt)
        {
            TItem cacheEntry;

            if (!_cache.TryGetValue(key, out cacheEntry))// Look for cache key.
            {
                SemaphoreSlim mylock = _locks.GetOrAdd(key, k => new SemaphoreSlim(1, 1));

                await mylock.WaitAsync();
                try
                {
                    if (!_cache.TryGetValue(key, out cacheEntry))
                    {
                        _cache.Set(key, item, opt);
                    }
                }
                finally
                {
                    mylock.Release();
                }
            }
            return cacheEntry;
        }

        #endregion

        #region 获取缓存 

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<TItem?> Get(object key)
        {
            TItem cacheEntry;

            if (_cache.TryGetValue(key, out cacheEntry))
            {
                SemaphoreSlim mylock = _locks.GetOrAdd(key, k => new SemaphoreSlim(1, 1));

                await mylock.WaitAsync();
                try
                {
                    if (_cache.TryGetValue(key, out cacheEntry))
                        return cacheEntry;
                }
                finally
                {
                    mylock.Release();
                }
            }
            return null;
        }

        #endregion 

        /// <summary>
        /// 移除
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public async Task<TItem> Remove(object key)
        {
            if (_cache.TryGetValue(key, out var tmp))// Look for cache key.
            {
                SemaphoreSlim mylock = _locks.GetOrAdd(key, k => new SemaphoreSlim(1, 1));

                await mylock.WaitAsync();
                try
                {
                    if (_cache.TryGetValue(key, out tmp))
                    {
                        _cache.Remove(key);
                        return (TItem)tmp;
                    }
                }
                finally
                {
                    mylock.Release();
                }
            }

            return default!;
        }

        #region 清除

        /// <summary>
        /// 清空缓存
        /// Compact 方法会对缓存的对象进行压缩，参数是个double，0.1 表示压缩 10% ，那么传 1.0 就是压缩 100%，那不就是 Clear All 么。
        /// Compact 方法根据注释它会按照已下优先级删除对象：
        ///         过期的对象
        ///         CacheItemPriority 设置的优先级，等级越高越不容易被删除
        ///         最近最少被使用的对象
        ///         绝对过期时间
        ///         滑动过期时间
        ///         大对象
        /// </summary>
        public void Clear()
        {
            _cache.Compact(1);

            _locks.Clear();
        }

        #endregion
    }
}
